﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EventReminder.Common;
using EventReminder.Client.EventReminderServiceRef;
using System.Security.Authentication;
using System.Threading;
using System.ComponentModel;
using System.ServiceModel;

namespace EventReminder.Client
{
    // handle all requests from Controller
    // make calls to Server [Proxy] as necessary
    public class EventReminderModel
    {
        private IEventReminderService _service;
        private DateTime _today;
        private string _sessionId;

        private EventData[] _events;
        public IEnumerable<EventData> Events
        {
            get { return _events; }
        }

        #region Observer
        public event EventHandler<EventsUpdatedArgs> EventsUpdated;
        protected void RaiseEventsUpdated(EventsUpdatedArgs e)
        {
            if (EventsUpdated != null)
                EventsUpdated(this, e);
        }

        public event EventHandler LoginFailed;
        protected void RaiseLoginFailed(EventArgs e)
        {
            if (LoginFailed != null)
                LoginFailed(this, e);
        }

        public event EventHandler LoginSucceeded;
        protected void RaiseLoginSucceeded(EventArgs args)
        {
            if (LoginSucceeded != null)
                LoginSucceeded(this, args);
        }

        public event EventHandler LogoutSucceeded;
        protected void RaiseLogoutSucceeded(EventArgs args)
        {
            if (LogoutSucceeded != null)
                LogoutSucceeded(this, args);
        }

        public event EventHandler EventAdded;
        protected void RaiseEventAdded(EventArgs args)
        {
            if (EventAdded != null)
                EventAdded(this, args);

        }

        public event EventHandler EventModified;
        protected void RaiseEventModified(EventArgs args)
        {
            if (EventModified != null)
                EventModified(this, args);
        }

        public event EventHandler EventDeleted;
        protected void RaiseEventDeleted(EventArgs args)
        {
            if (EventDeleted != null)
                EventDeleted(this, args);
        }
        #endregion

        public EventReminderModel()
        {
            _service = new EventReminderRemoteProxy();
            _events = new EventData[0];
        }

        public void Login(string userName, string password)
        {
            try
            {
                string response = _service.Login(userName, password);

                if (response == ReturnCodes.INVALID_USERNAME_OR_PW)
                {
                    //throw new AuthenticationException("Invalid username or password!");
                    RaiseLoginFailed(EventArgs.Empty);
                }
                else
                {
                    // save SessionID here?
                    _sessionId = response;
                    RaiseLoginSucceeded(EventArgs.Empty);
                }
            }
            catch (FaultException)
            {
                // back out carefully
            }
        }

        public void GetEvents(DateTime day)
        {
            try
            {
                _today = day;
                _events = _service.GetEvents(day, _sessionId);
                RaiseEventsUpdated(new EventsUpdatedArgs(_events));
            }
            catch (FaultException)
            {
                // back out carefully
            }
        }

        public void GetEventsThreaded(DateTime day)
        {
            // TODO: move all of this threading to a separate class (Virtual Proxy?)
            try
            {
                _today = day;

                BackgroundWorker bg = new BackgroundWorker();
                bg.DoWork += new DoWorkEventHandler(HandleDoWork);
                bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(HandleWorkerThreadCompleted);
                bg.RunWorkerAsync();
            }
            catch (FaultException)
            {
                // back out carefully
            }
        }

        private void HandleDoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                EventData[] events = _service.GetEvents(_today, _sessionId);

                lock (_events)
                {
#if DEBUG
                    Thread.Sleep(3000); // sleep 3 seconds to simulate long delay
#endif
                    _events = events; // kinda breaks the lock...
                }
            }
            catch (FaultException)
            {
                // back out carefully
            }
        }

        private void HandleWorkerThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            lock (_events)
            {
                if (_events.Length == 0) // nothing to bother with
                    return;

                // verify that today is still the same day (ie., user didn't quit and click another while the thread was running)
                if (!_today.MatchesToTheDay(_events[0].Date))
                    return;

                // update events
                RaiseEventsUpdated(new EventsUpdatedArgs(_events));
            }

            // unsubscribe
            BackgroundWorker bg = sender as BackgroundWorker;
            if (bg != null)
            {
                bg.DoWork -= new DoWorkEventHandler(HandleDoWork);
                bg.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(HandleWorkerThreadCompleted);
            }
        }

        public void SaveNewEvent(EventData eventData)
        {
            try
            {
                _service.AddEvent(eventData, _sessionId);
                RaiseEventAdded(EventArgs.Empty);
                // raise EventsUpdated
            }
            catch (FaultException)
            {
                // back out carefully
            }
        }

        public void ModifyEvent(EventData oldVersion, EventData updatedVersion)
        {
            try
            {
                _service.ModifyEvent(oldVersion, updatedVersion, _sessionId);
                RaiseEventModified(EventArgs.Empty);
                // raise EventsUpdated
            }
            catch (FaultException)
            {
                // back out carefully
            }
        }

        public void DeleteEvent(EventData eventData)
        {
            try
            {
                _service.DeleteEvent(eventData, _sessionId);
                RaiseEventDeleted(EventArgs.Empty);
                // raise EventsUpdated
            }
            catch (FaultException)
            {
                // back out carefully
            }
        }

        public void Register(string userName, string password, string firstName, string lastName, string email)
        {
            try
            {
                string response = _service.Register(userName, password, firstName, lastName, email);
                //RaiseLoginSucceeded(EventArgs.Empty);
                if (response == ReturnCodes.INVALID_USERNAME_OR_PW)
                {
                    //throw new AuthenticationException("Invalid username or password!");
                    RaiseLoginFailed(EventArgs.Empty);
                    //Need to add a Register Logon Fail Event here
                }
                else
                {
                    // save SessionID here?
                    _sessionId = response;
                    RaiseLoginSucceeded(EventArgs.Empty);
                }
                // raise Login Successful event
                // Problem: this concept expects me to know that successful registration results in auto-login
                // - this couples EventReminderModel to 
                // - consier handling this server-side when I can pass messages both ways and force
                //    client-side events to be raised from the server side
            }
            catch (FaultException) // exceptions thrown server-side result in FaultException client-side
            {
                // back out carefully
            }
        }

        public void Logout()
        {
            try
            {
                if (_sessionId != null)
                {
                    _service.Logout(_sessionId);
                }

                RaiseLogoutSucceeded(EventArgs.Empty);
            }
            catch (FaultException)
            {
                // back out slowly
            }
        }
    }
}
